vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Woche 1

Tag 6


Bedingungen und Schleifen

Mit Hilfe von Bedingungen und Schleifen steuern Sie die Ausführung von Anweisungsblöcken in Perl-Skripten. Ohne solche Strukturen würde Ihr Perl-Skript einfach von oben nach unten durchlaufen und nacheinander alle Anweisungen abarbeiten, bis es zu Ende ist. Sie könnten nicht überprüfen, ob etwas einen bestimmten Wert hat, und dann gegebenenfalls zu einem anderen Teil des Codes abzweigen, Sie könnten nicht festlegen, ob und wie oft ein Anweisungsblock noch einmal ausgeführt wird - ohne Kontrollstrukturen wäre Perl sehr langweilig.

In dieser Lektion behandeln wir die verschiedenen Bedingungs- und Schleifenkonstrukte, die Sie für Ihre Perl-Skripts brauchen. Unsere heutigen Themen sind:

Komplexe Anweisungen und Blöcke

Bedingungen und Schleifen werden manchmal auch komplexe Anweisungen genannt, und zwar deshalb, weil Bedingungen und Schleifen im Vergleich zu einzelnen Anweisungen, die mit einem Semikolon enden (wie zum Beispiel $x = 5; oder $array[0] = "erstes";), wie soll ich sagen ... eben komplexer sind. Der wahrscheinlich bedeutendste Unterschied zwischen einfachen und komplexen Anweisungen ist allerdings, dass letztere mit ganzen Blöcken von Perl-Code arbeiten.

Ein Block ist einfach eine in geschweifte Klammern {} gesetzte Folge von beliebigen Perl-Anweisungen. Innerhalb eines Blocks können einfache Anweisungen oder andere Blöcke stehen, also alles, was auch außerhalb des Blocks auftauchen kann. Wie Anweisungen in einem Skript werden die Anweisungen in einem Block nacheinander ausgeführt. Zum Beispiel:

while (Bedingung) { # Beginn des Blocks
Anweisung;
Anweisung;
if (Bedingung) { # Beginn des if-Blocks
Anweisung;
} # Ende des if-Blocks
# ... weitere Anweisungen
} # Ende des Blocks

Eine andere Eigenschaft von Blöcken ist, dass Perl hinter der letzten Anweisung im Block nicht unbedingt ein Semikolon verlangt. Es ist aber eine gute Idee, das Semikolon trotzdem zu setzen - dann müssen Sie nicht mehr darauf achten, wenn Sie später weitere Anweisungen hinzufügen.

Blöcke, die nicht an eine Bedingung oder Schleife gebunden sind, werden bare blocks oder freistehende Blöcke genannt und nur einmal ausgeführt. Bare blocks können durchaus nützlich sein, besonders wenn Sie sie mit einem Label versehen, doch jetzt werden wir uns erst einmal auf die Blöcke konzentrieren, die zu komplexen Anweisungen gehören.

Bedingungen

Mit Hilfe von Bedingungsanweisungen können Sie festlegen, welche Blöcke Perl ausführen soll, wenn ein Ausdruck einen bestimmten Wert hat. Sie setzen eine Bedingung auf, und Perl überprüft, ob diese Bedingung erfüllt ist, das heißt, ob der Bedingungsausdruck wahr ist. Wenn der Ausdruck wahr ist, führt Perl den direkt folgenden Block aus, wenn er falsch ist, überspringt Perl diesen Block und macht beim nächsten Block oder Teil des Skripts weiter. Anders als bei Schleifen wird jeder Block nur einmal ausgeführt.

if, if...else, und if...elsif

Die häufigste Formen der Bedingung sind if (wenn) und seine Varianten if...else (wenn...sonst) und if...elsif (wenn...sonst wenn). Die if-Anweisung sieht wie folgt aus:

if ( Bedingungsausdruck) {
# Anweisungen
}

Bedingungsausdruck kann jeder beliebige Ausdruck sein; er wird in Booleschem skalaren Kontext auf seinen Wahrheitswert überprüft. Sie erinnern sich, dass alles außer "", 0 und undef als wahr angesehen wird. Ist der Bedingungsausdruck wahr, wird der Anweisungsblock ausgeführt. Ist er falsch, passiert gar nichts, und Perl macht mit der nächsten Anweisung unter dem if-Block weiter.

Beachten Sie, dass anders als in C oder Java auf die Bedingung (auch bei else und elsif) immer ein Block folgen muss, auch wenn er nur eine einzige Anweisung enthält. Sie müssen die geschweiften Klammern immer setzen - aber nicht unbedingt in verschiedenen Zeilen, wie ich es eben gezeigt habe. Wo die Klammern stehen, bleibt Ihrem Geschmack überlassen.

Um einen alternativen Block ausführen zu lassen, wenn die Bedingung nicht erfüllt ist, verwenden Sie if...else:

if ( Bedingungsausdruck ) {
# Anweisungen, die ausgefuehrt werden,
# wenn Bedingungsausdruck wahr ist
} else {
# Anweisungen, die ausgefuehrt werden,
# wenn Bedingungsausdruck falsch ist
}

Wie in anderen Sprachen auch, können Sie if und else auch ineinander verschachteln:

if ( Bedingungsausdruck 1 ) {
# Anweisung 1
} else {
if ( Bedingungsausdruck 2 ) {
# Anweisung 2
} else {
if ( Bedingungsausdruck 3 ) {
# Anweisung 3
} else {
# und so weiter...
}
}
}

Um Ihnen etwas Tipparbeit zu ersparen oder unübersichtlich viele Einzüge zu vermeiden, bietet Perl eine dritte Form von if-Bedingungen, das elsif, das diese Art von Operationen um einiges kompakter macht:

if ( Bedingungsausdruck 1 ) {
# Anweisung 1
} elsif ( Bedingungsausdruck 2 ) {
# Anweisung 2
} elsif ( Bedingungsausdruck 3 ) {
# Anweisung 3
} else (
# Anweisung fuer alle anderen Faelle
}

Beachten Sie, dass sobald die Bedingung eines elsif wahr ist, alle noch folgenden elsif-Blöcke übersprungen, das heißt nicht einmal ausgewertet werden. Bei verschachtelten if...else-Anweisungen ist es ebenso: In jeder Folge von Blöcken wird jeweils nur der erste ausgeführt, dessen Bedingung erfüllt ist.

Was ist mit Fallunterscheidungskonstrukten wie switch oder case? Perl hat von sich aus keine Anweisung für switch (das ist wahrscheinlich das einzige Konstrukt, für das andere Sprachen eine eigene Anweisung haben, Perl aber nicht). Allerdings gibt es verschiedene Möglichkeiten, mit vorhandenen Perl-Konstrukten eine switch- Anweisung zu »simulieren«. Ein paar davon werden wir im Abschnitt »Vertiefung« am Ende dieser Lektion durchgehen.

unless

Die unless-Anweisung ist eine Art umgekehrtes if. Manchmal soll eine Operation nur ausgeführt werden, wenn eine Bedingung nicht erfüllt ist - was bedeutet, dass in einer normalen if...else-Anweisung all das gute Zeug in den else-Teil wandern würde wie hier:

if ( Bedingungsausdruck ) {
# mache nichts
} else {
# mache das hier
}

Das ist nicht unbedingt die optimale Lösung. Sie könnten natürlich den Bedingungsausdruck negieren (mit not oder !):

if (not Bedingungsausdruck ) {   
# mache das hier
}

So müßten Sie es in anderen Sprachen machen und Ihre Denkweise an die Syntax anpassen. Perl aber zieht es vor, wenn Sie so denken, wie Sie zu denken gewohnt sind, und bietet Ihnen deshalb eine Alternative. Wenn Sie denken: »Mache das und das, außer unter dieser Bedingung«, also eine Operation nur ausführen möchten, wenn diese Bedingung nicht erfüllt ist, nehmen Sie einfach unless (vom Englischen unless, wenn nicht oder außer wenn):

unless ( Bedingungsausdruck ) {
# mache das hier
}

Mit diesem unless wird der Block nur dann ausgeführt, wenn der Bedingungsausdruck falsch ist. Ist er aber wahr, wird der Block übersprungen. Sie können einem unless auch ein else hinzufügen, wenn Sie möchten (aber kein elsif, und so etwas wie elsunless gibt es zum Glück auch nicht).

Der Bedingungsoperator

Manche Bedingungen sind so kurz, dass es viel zuviel Aufwand wäre, all diese Klammern und Worte auf sie zu verschwenden. Manchmal ist es sinnvoll, eine Bedingung in einen anderen Ausdruck zu betten (was mit if oder unless nicht möglich ist, weil diese keine Werte zurückliefern). Verwenden Sie in solchen Situationen den Bedingungsoperator ?...:.... Wie bei if...else brauchen Sie einen Bedingungsausdruck und zwei Angaben - was Perl bei erfüllter und bei nicht erfüllter Bedingung tun soll. Das Ganze schreiben Sie dann in folgender Form:

Bedingungsausdruck ? wert_wenn_wahr : wert_wenn_falsch;

Hier wird der Bedingungsausdruck auf seine Wahrheit überprüft, genau wie bei if, und wenn er wahr ist, dann wird der Ausdruck wert_wenn_wahr ausgewertet (und der Wert zurückgegeben), anderenfalls wird wert_wenn_falsch ausgewertet und zurückgegeben. Anders als bei if und unless sind wert_wenn_wahr und wert_wenn_falsch einzelne Ausdrücke, keine Blöcke. Zum Beispiel können Sie mit der folgenden Zeile ganz schnell den höheren von zwei Werten herausfinden:

$max = $x > $y ? $x : $y;

Dieser Ausdruck sieht nach, ob der Wert von $x größer ist als der von $y. Wenn ja, gibt er $x zurück. Wenn $x kleiner oder gleich $y ist, gibt er $y zurück. Der zurückgegebene Wert wird dann der Variablen $max zugewiesen. Mit if und else würde der gleiche Vorgang so aussehen:

if ($x > $y) {
$max = $x;
} else {
$max = $y;
}

Der Bedingungsoperator wird manchmal auch triadischer Operator genannt, weil er drei Operanden hat (monadische Operatoren haben einen, dyadische zwei und triadische drei Operanden).

Kontrollflußsteuerung mit logischen Operatoren

Am Tag 2 habe ich Ihnen Perls logische Operatoren &&, ||, and und or erklärt und kurz erwähnt, dass Sie damit Bedingungsanweisungen konstruieren können. Lassen Sie uns diese Operatoren hier noch einmal betrachten, damit Sie ein Gefühl dafür bekommen, wie sie arbeiten. Nehmen Sie den folgenden Ausdruck:

$wert = $dies || $das;

Um zu verstehen, wie dieser Ausdruck funktioniert, müssen Sie sich an zwei Eigenschaften von logischen Operatoren erinnern: dass sie »kurzschließen« und dass sie den Wert des zuletzt ausgewerteten Ausdrucks zurückgeben. So wird in dem obigen Beispiel zuerst $dies, der linke Operand von ||, überprüft, und dann gibt es drei Möglichkeiten:

Mit if...else könnten Sie diese Anweisung wie folgt schreiben:

if ($dies) { $wert = $dies; }
else {$wert = $das; }

Mit dem Bedingungsoperator könnten Sie schreiben:

$wert = $dies ? $dies : $das

Aber beide brauchen mehr Platz und sind schwieriger zu begreifen - zumindest schwieriger als der logische Ausdruck, der sich fast wie Klartext liest: dies oder das. Und schon sind Sie fertig.

Wie ich am Tag 2 schon erwähnt habe, werden Ihnen solche für eine Entscheidung eingesetzten logischen Operatoren am häufigsten beim Öffnen von Dateien begegnen:

open(DATEI, "Dateiname") or die "Kann Datei nicht oeffnen\n";

Wenn die Funktion open eine Datei erfolgreich geöffnet hat, gibt sie wahr zurück, und deswegen wird der Teil nach dem or übersprungen, die Funktion diestirb«) also gar nicht erst ausgeführt. Mehr hierzu am Tag 15.

while-Schleifen

Mit den verschiedenen if-Anweisungen in Perl steuert man den Programmfluß, indem man (je nachdem, ob eine Bedingung erfüllt ist oder nicht) zu verschiedenen Teilen des Skripts abzweigt. Die zweite Steuerungsmöglichkeit sind Schleifen - die ein und denselben Anweisungsblock immer wieder ausführen und erst dann aufhören, wenn eine bestimmte Bedingung erfüllt ist. Perl hat zwei generelle Arten von Schleifen, die beide ungefähr dasselbe machen: while-Schleifen laufen solange, bis eine Bedingung erfüllt ist, bei for-Schleifen ist die Anzahl der Durchläufe von Anfang an festgelegt. Man kann jede while-Schleife auch als for-Schleife formulieren und umgekehrt, doch paßt je nach Situation meist die eine besser als die andere.

Wir beginnen mit den while-Schleifen. Davon hat Perl drei verschiedene: while, do...while und until.

while

Die Grundform einer Schleife in Perl ist die while-Schleife mit einem Bedingungsausdruck und einem Anweisungsblock:

while ( Bedingungsausdruck ) {
# zu wiederholende Anweisungen
}

In der while-Schleife wird der Bedingungsausdruck ausgewertet, und wenn er wahr ist, werden die Anweisungen im Block ausgeführt. Danach wird der Bedingungsausdruck wieder ausgewertet, und wenn er immer noch wahr ist, wird der Anweisungsblock wieder ausgeführt. Dieser Vorgang wiederholt sich so lange, bis der Bedingungsausdruck falsch ergibt. Als Beispiel zeige ich Ihnen noch einmal die while- Schleife aus dem Krümelmonster-Skript, das Sie bereits am ersten Tag gesehen haben:

while ( $kekse ne "KEKSE") {
print 'ich will KEKSE: ';
chomp($kekse = <STDIN>);
}

Hier wiederholt sich der Eingabevorgang (die print- und die <STDIN>-Anweisung) so lange, bis die Eingabe den String "KEKSE" enthält. Sie könnten diese Schleifenanweisung auch so lesen: »Solange der Wert von $kekse nicht gleich dem String "KEKSE" ist, tue folgendes.«

Hier ein anderes Beispiel von Tag 4, das mit einer temporären Variablen $i für den Array-Index ein Array durchläuft:

$i = 0;
while ($i <= $#array) {
print $array[$i++], "\n";
}

In diesem Fall ist die Bedingung, dass $i kleiner oder gleich dem größten Array-Index ist. Innerhalb des Schleifenblocks geben wir das aktuelle Array-Element aus und inkrementieren $i. So bestimmen wir, wie oft die Schleife durchlaufen werden soll: solange wie $i kleiner oder gleich dem größten Index in @array ist, also $#array + 1, (weil wir ja bei 0 anfangen).

Beachten Sie beim Schreiben von while-Schleifen, dass innerhalb der Schleife etwas passieren muss, dass sie näher an ihr Ende bringt. Wenn Sie hier zum Beispiel vergessen, $i zu erhöhen, wird die Schleifenbedingung nie erfüllt, und die Schleife läuft und läuft und läuft ohne Ende.

Schleifen, die nicht enden, werden Endlosschleifen genannt und können - wenn man sie bewußt verwendet - durchaus von Nutzen sein. Zum Beispiel ist eine while- Schleife ohne Bedingung eine absichtlich endlose Schleife. Sie haben ein paar davon in den bisherigen Beispielen gesehen. Diese hier ist aus den Statistikskripten:

while () {
print 'Bitte eine Zahl eingeben: ';
chomp ($input = <STDIN>);
if ($input ne '') {
$zahlen[$count] = $input;
$count++;
$sum += $input;
}
else { last; }
}

Diese Schleife liest bei jedem Durchlauf eine Zeile von der Standardeingabe und kann niemals aufgrund einer Schleifenbedingung enden, da es keine Bedingung gibt. Doch innerhalb der Schleife überprüfen wir die Eingabe mit if...else. Wenn $input ein leerer String ist, unsere if-Bedingung also nicht erfüllt ist, wird der else-Block ausgeführt, der nur aus dem Schleifensteuerbefehl last besteht. Dieses last bricht die Schleife ab und geht zum nächsten Teil des Skripts weiter. Es gibt in Perl drei solcher Schleifensteuerbefehle: last, next und redo, die wir später in diesem Kapitel, im Abschnitt »Schleifen steuern«, genauer betrachten werden.

Ich hätte diese Schleife auch so schreiben können, dass das while eine richtige Bedingung hätte und zum richtigen Zeitpunkt abbrechen würde. In diesem speziellen Beispiel fand ich es aber einfacher, die Schleife eben auf diese Art zu konstruieren. Perl zwingt Ihnen auch bei Schleifen oder Bedingungen kein bestimmtes Denkmuster auf, Sie können Ihr Skript so bauen, wie Sie es für die jeweilige Aufgabe am besten halten.

until

Genau wie unless das Gegenteil von if ist, ist until das Gegenteil von while. Until sieht genauso aus wie while, mit einem Bedingungsausdruck und einem Block:

until ( Bedingungsausdruck ) {
# Anweisungen
}

Der einzige Unterschied ist die Bedeutung der Bedingung - mit while wird die Schleife so lange durchlaufen, wie die der Bedingungsausdruck wahr ist. Mit until wird sie so lange ausgeführt, wie der Bedingungsausdruck falsch ist. Until ist englisch und heißt bis - »Bis diese Bedingung wahr ist, tue das hier.«

do

Die dritte Form von while-Schleifen ist do. Bei while und until wird die Schleifenbedingung ausgewertet, bevor der Block ausgeführt wird - wenn der Bedingungsausdruck also von Anfang an falsch (oder bei unless wahr) ist, bricht die Schleife sofort ab und macht gar nichts. Manchmal aber möchten Sie einen Anweisungsblock zunächst einmal ausführen und erst danach entscheiden, wie es weitergeht. Hierfür gibt es do. do-Schleifen haben einen anderen Aufbau als while- und until-Schleifen und sehen etwa wie folgt aus (beachten Sie das Semikolon am Ende, bei do ist es Pflicht):

do {
# Schleifenblock
} while (Bedingungsausdruck);

Dasselbe geht auch mit until:

do {
# Schleifenblock
} until (Bedingungsausdruck);

Mit diesen beiden Anweisungen werden die Blöcke vor der Auswertung des Bedingungsausdrucks ausgeführt. Selbst wenn der Bedingungsausdruck falsch (bei while) oder wahr (bei until) ist, wird der Anweisungsblock mindestens einmal ausgeführt.

In Wirklichkeit ist do eine Funktion, die hier nur so tut, als wäre sie eine Schleife (deshalb brauchen Sie auch das Semikolon). Meistens verhält do sich genau wie eine Schleife, nur wenn Sie mit Schleifensteuerbefehlen (wie last oder next) oder mit Labels (Sprungmarken) arbeiten wollen, müssen Sie statt do eine »echte« while- oder until-Schleife verwenden. Schleifensteuerbefehle und Labels besprechen wir noch in dieser Lektion.

Ein Beispiel: Zahlen raten

In diesem Beispiel spielen wir ein kleines Spiel. Perl bittet Sie um eine Zahl, »zieht« dann eine Zufallszahl zwischen 1 und Ihrer Zahl, und Sie sollen raten, welche es gezogen hat, zum Beispiel:

% zahlraten.pl
Geben Sie die hoechste Zahl ein: 50
Ihr Tipp? (eine Zahl zwischen 1 und 50): 25
Zu hoch!
Ihr Tipp? (eine Zahl zwischen 1 und 50): 10
Zu niedrig!
Ihr Tipp? (eine Zahl zwischen 1 und 50): 17
Zu hoch!
Ihr Tipp? (eine Zahl zwischen 1 und 50): 13
Zu hoch!
Ihr Tipp? (eine Zahl zwischen 1 und 50): 12
Richtig!
Gratuliere! Sie haben mit 5 Versuchen die richtige Zahl erraten.
%

Das Skript dazu arbeitet mit zwei endlosen while-Schleifen, einer Menge if- Bedingungen und der rand-Funktion zur Ermittlung einer Zufallszahl. Listing 6.1 zeigt den Code.

Listing 6.1: Das Skript zahlraten.pl

1:  #!/usr/bin/perl -w
2:
3: $top = 0; # hoechste Zahl
4: $num = 0; # Zufallszahl
5: $count = 0; # zaehlt Versuche
6: $guess = ""; # aktueller Tipp
7:
8: while () {
9: print 'Geben Sie die hoechste Zahl ein: ';
10: chomp($top = <STDIN>);
11: if ($top == 0 || $top eq '0') {
12: print "Das ist keine gute Zahl.\n";
13: }
14: else { last; }
15: }
16:
17: srand;
18: $num = int(rand $top) + 1;
19:
20: while () {
21: print " Ihr Tipp? (eine Zahl zwischen 1 und $top): ";
22: chomp($guess = <STDIN>);
23: if ($guess == 0 || $guess eq '0') {
24: print "Das ist keine gute Zahl.\n";
25: } elsif ($guess < $num) {
26: print "Zu niedrig!\n";
27: $count++;
28: } elsif ($guess > $num) {
29: print "Zu hoch!\n";
30: $count++;
31: } else {
32: print "\a\aRichtig! \n";
33: $count++;
34: last;
35: }
36: }
37: print "Gratuliere! Sie haben mit $count Versuchen";
38: print " die richtige Zahl erraten.\n";

Dieses Skript hat vier Teile: Initialisierung, Eingabe der höchsten Zahl, Auswahl der zu ratenden Zahl und dann das Raten selbst. Den Initialisierungsteil überspringe ich diesmal ganz; Sie werden mittlerweile wissen, wie man Skalarvariablen zuweist.

In Zeile 8 bis 15 erfragen wir, wie hoch die Zufallszahl höchstens sein darf. Diese while-Schleife ist endlos, doch die Bedingung in Zeile 11 soll sicherstellen, dass Sie nicht 0 eingeben. Tun Sie es doch, wird eben nicht das last im else-Block, sondern die Eingabeschleife von vorn ausgeführt. Denken Sie daran, dass Perl bei der Umwandlung von Strings in Zahlen einen String in 0 konvertiert, wenn es keine numerischen Daten in ihm findet. Deswegen fängt diese Bedingung sowohl eine 0 als auch alle anderen nichtnumerischen Eingaben ab. Perl-Warnungen würden sich bei der Eingabe eines Strings trotzdem über »not a number« (»keine Zahl«) beschweren. Wenn wir uns in ein paar Tagen mit Mustervergleichen (Pattern Matching) befassen, werden Sie einen besseren Weg kennenlernen, diese Warnungen zu umgehen.

Jedenfalls generieren wir, sobald wir eine brauchbare Zahl haben, in Zeile 17 und 18 die geheime Zahl, und zwar mit den eingebauten Funktionen srand und rand. Ohne Argumente setzt die srand-Funktion den Zufallsgenerator auf die aktuelle Uhrzeit, damit wir jedesmal andere Zahlen bekommen (ansonsten wäre es in der Tat ein sehr langweiliges Spiel). Die Funktion rand generiert dann eine Zufallszahl zwischen 0 und einem Argument, hier unserem $top (einschließlich 0 ohne $top). Wir schneiden diese Zahl auf einen Integer zu, addieren 1, damit wir keine Nullen, aber gelegentlich auch die eingegebene Höchstzahl erhalten, und speichern sie in der Variablen $num.

In den Zeilen 20 bis 34 wird geraten. Hier überprüfen wir mit if...elsif- Anweisungen drei Dinge:

Wenn der Vorschlag weder zu niedrig noch zu hoch, aber eine gültige Zahl ist, dann muss es die richtige Zahl sein - also piepen wir zweimal (okay, Sie vielleicht nicht, aber die Escape-Sequenz \a gibt einen Piepton aus), Perl schreibt einen Glückwünsch auf den Bildschirm und hört auf.

Während der Benutzer Zahlen rät, merken wir uns die Anzahl der Versuche in einer $count-Variablen. Wir wollen aber nur gültige Versuche zählen, also inkrementieren wir $count nur in den drei Fällen gültiger Zahlen (zu niedrig, zu hoch, richtig). $count wird dann mit der Glückwunschmeldung ausgegeben.

for-Schleifen

Geht es um die Wiederholung eines Anweisungsblocks, bietet while als »Mutter aller Schleifen« immer einen Weg - sie führt einen Block immer wieder aus, und zwar so lange (bzw. bis) die Schleifenbedingung erfüllt ist. Die zweite Art von Schleife, die for- Schleife, löst das gleiche Problem auf etwas andere Art und Weise. Bei for-Schleifen wird der Anweisungsblock n-mal hintereinander ausgeführt, dann wird die Schleife beendet.

Sie könnten jede while-Schleife auch als for-Schleife formulieren und umgekehrt. Aber für manche Aufgaben bietet sich die eine Form eher an als die andere.

Perl kennt zwei for-Schleifen: eine allgemeine C-ähnliche for-Schleife und eine dem Shell-Skripting entliehene foreach-Schleife, die einen Block »für jedes« (for each) Element einer Liste wiederholt.

for

Die for-Schleife ist in Perl dieselbe wie in C. Sie setzen eine Lauf- oder Zählervariable (zum Beispiel $i) auf einen beliebigen Startwert, überprüfen eine Bedingung, führen den Schleifenblock aus, wenn diese erfüllt ist, ändern dann den Wert der Laufvariablen, überprüfen wieder die Bedingung, durchlaufen die Schleife gegebenenfalls von neuem, ändern wieder die Laufvariable - und so weiter:

for ( Startwert; Bedingungsausdruck; Aenderung ) {
# Anweisungen
}

In diesem Beispiel ist Startwert der Ausdruck zum Initialisieren der Laufvariablen, der Bedingungsausdruck entscheidet, ob die Iteration weitergeht, und Aenderung bestimmt, wie der Wert der Laufvariablen nach jedem Schleifendurchlauf geändert wird. Beim ersten Durchlauf wird der Zähler initialisiert, die Bedingung ausgewertet und, wenn sie wahr liefert, der Anweisungsblock ausgeführt. Beim zweiten Durchlauf wird die Änderungsanweisung ausgeführt, die Bedingung wieder überprüft und wenn sie immer noch wahr ist, der Block wieder ausgeführt. So geht es immer weiter, bis der Bedingungsausdruck ein falsch zurückgibt.

Für fünf Schleifendurchläufe könnten Sie zum Beispiel eine for-Schleife wie diese verwenden:

for ( $i = 1; $i <= 5; $i++) {
print "Durchlauf $i\n";
}

Dieser Code-Schnipsel produziert folgende Ausgabe:

Durchlauf 1
Durchlauf 2
Durchlauf 3
Durchlauf 4
Durchlauf 5

Sie könnten diese Schleife selbstverständlich auch als while-Schleife schreiben:

$i = 1;
while ($i <= 5) {
print "Durchlauf $i\n";
$i++;
}

Beide Schleifen würden fünfmal durchlaufen, doch die for-Schleife ist kompakter und übersichtlicher. Sie listet sozusagen fein säuberlich auf, wo sie anfängt, wo sie aufhört und welche Schritte sie dazwischen unternimmt. Für manche Aufgaben eignet sie sich besser als eine while-Schleife - beispielsweise, wenn Sie einen begrenzten Satz von Werten durchgehen, etwa zum Bearbeiten aller Elemente in einem Array.

Sie können die Initialisierung, den Bedingungsausdruck oder die Änderungsanweisung oder alle drei im oberen Teil der for-Schleife weglassen, nicht jedoch die Semikola. Eine endlose for-Schleife könnte zum Beispiel folgendermaßen aussehen:

for ($i = 0; ; $i++) {
# Anweisungen
}

Oder einfach so:

for (;;) {
# Anweisungen
}

Die erste Schleife initialisiert den Schleifenzähler $i und erhöht ihn nach jedem Durchlauf um 1. Die zweite Schleife hat nicht einmal eine Zählervariable, sie wird einfach nur durchlaufen. Um aus diesen Schleifen herauszukommen, müssen Sie die Abbruchbedingung innerhalb der Blöcke festlegen.

Sie wollen die Schleife über mehrere Zähler laufen lassen? Setzen Sie einfach Kommas zwischen Ihre Zählerausdrücke:

for ($i=0, $j=1; $i < 10, j$ < $total; $i++, $j++ ) {
# Anweisungen
}

In diesem Fall wird die Schleife abgebrochen, wenn einer der beiden Bedingungsausdrücke wahr zurückgibt.

foreach

Die for-Schleife bietet sich an, wenn Sie auf einen konkreten Endpunkt überprüfen können - zum Beispiel den höchsten Index in einem Array oder eine bestimmte Zahl. foreach ist eine Kurzversion von for ohne expliziten Zähler. foreach durchläuft eine Liste und führt den Anweisungsblock so viele Male aus, wie diese Liste Elemente hat.

Sie kennen foreach bereits - gestern haben wir uns damit durch Listen von Hash- Schlüsseln gearbeitet. Hier nun die ausführliche »Gebrauchsanweisung« für foreach: Sie übergeben der foreach-Schleife eine Liste - zum Beispiel eine Liste aller Schlüssel in einem Hash oder einen Bereich. foreach durchläuft die Liste, setzt dabei eine temporäre Variable nacheinander auf den Wert jedes Elements und führt für jedes Element in der Liste den Anweisungsblock aus.

Hier ein simples Äquivalent zur for-Schleife von vorhin, die den Zähler fünfmal ausgegeben hat:

foreach $zahl (1 .. 5) {
print "Durchlauf $zahl\n";
}

Hier erstellt der Bereichsoperator .. eine Liste der Zahlen 1 bis 5, und die foreach- Schleife arbeitet sich durch diese Liste, wobei sie die Zahlen nacheinander der Variablen $zahl zuweist.

Foreach-Schleifen eignen sich außerordentlich gut zum Durchlaufen von Listen, zum Beispiel um die Schlüssel und Werte eines Hash auszugeben, wie Sie gestern gelernt haben:

foreach $key (sort keys %hashname) {
print "Schlüssel: $key Wert: $hashname{$key}\n";
}

Wie Sie sehen, habe ich die $key-Variablen hier nicht initialisiert (auch die $zahl- Variable im vorangehenden Beispiel wurde nicht initialisiert). Das ist nicht nötig, weil die Variable zwischen foreach und der Liste eine lokale Variable innerhalb der Schleife ist - das heißt, vor und nach der Schleife existiert sie gar nicht. Falls sie doch schon existiert, Sie also vor der Schleife eine gleichnamige Variable verwenden, benutzt foreach sie nur vorübergehend und stellt ihren ursprünglichen Wert wieder her, sobald die Schleife verlassen wird. Sie können sich die foreach-Variable als eine Art »Kritzel«-Variable vorstellen, die einzig und allein dem kurzen Speichern von Elementen dient und beim Schleifenende weggeworfen wird.

Wenn Sie die Werte der Listenelemente gar nicht brauchen, sondern lediglich die Liste durchgehen möchten, können Sie diese Variable auch einfach weglassen.

Schleifen steuern

In while- und for-Schleifen stehen die Bedingungen, unter denen die Schleife durchlaufen bzw. abgebrochen werden soll, ganz am Anfang. In vielen Fällen ist das auch genau das Richtige. Wenn Sie aber mit komplexeren Schleifen arbeiten oder mit Endlosschleifen spielen, wie ich es in einigen der bisherigen Beispiele getan habe, möchten Sie vielleicht auch mitten in der Schleife entscheiden können, wie es von diesem Punkt aus weitergehen soll. Und natürlich hat Perl da etwas für Sie: Schleifensteuerbefehle.

Wie ich schon erwähnt habe, können Sie in do-Schleifen keine Schleifensteuerbefehle oder Labels (Sprungmarken) einsetzen. Dafür müßten Sie die do-Schleife zu einer while- , until- , for- oder foreach-Schleife umschreiben.

Mit Schleifensteuerbefehlen steuern Sie den Ablauf einer Schleife. Zwei von ihnen haben Sie bereits gesehen: next und last, um die Schleife neu zu starten oder sie komplett abzubrechen. Außerdem gibt es in Perl redo und Labels, mit denen Sie Ihren Schleifen einen Namen geben können.

last, next und redo

Die Grundbausteine der Schleifensteuerung sind die Schlüsselwörter last, next und redo. Sobald eins von ihnen in einer while- oder for-Schleife auftritt, unterbricht Perl die normale Schleifenausführung.

Sie können jeden dieser drei Befehle mit einem Label oder für sich allein verwenden. Mit einem Label beziehen sie sich auf die mit diesem Label versehene Schleife ohne Label auf die innerste Schleife (mehr über Labels später). Im einzelnen bewirken sie folgendes:

Schauen wir uns noch einmal das Zahlenratespiel an. Wir könnten die zweite while- Schleife auch so schreiben:

while () {
print "Ihr Tipp? (eine Zahl zwischen 1 und $top): ";
chomp($guess = <STDIN>);
if ($guess == 0 || $guess eq '0') { # wenn Eingabe 0 oder String
print "Das ist keine gute Zahl.\n";
next;
}
if ($guess < $num) { # wenn Eingabe zu niedrig
print "Zu niedrig!\n";
$count++;
next;
}
if ($guess > $num) { # wenn Eingabe zu hoch
print "Zu hoch!\n";
$count++;
next;
}
$count++;
last;
}

Weil diese while-Schleife endlos ist, müssen wir mit Schleifensteuerbefehlen innerhalb des Schleifenkörpers festlegen, wann sie abgebrochen werden soll - in unserem Fall, wenn die richtige Zahl eingegeben wurde. In dem Schleifenblock wird die Eingabe auf drei verschiedene Dinge überprüft: ob sie gleich 0 ist, ob sie kleiner als die geheime Zahl ist oder ob sie größer als die geheime Zahl ist. In jeder dieser if-Anweisungen überspringen wir, wenn eins dieser Kriterien zutrifft, mit next sofort die restlichen Anweisungen im Block und gehen zurück zum Schleifenanfang (stünde dort ein Bedingungsausdruck, würden wir ihn jetzt aufs neue auswerten). Wenn die eingegebene Zahl allen drei Überprüfungen standhält, dann ist es die richtige, und wir können die Schleife mit last abbrechen.

Im allgemeinen sind Schleifensteuerbefehle nur notwendig, wenn Sie innerhalb der Schleife auf bestimmte Bedingungen überprüfen, die den normalen Ablauf der Schleife unterbrechen sollen, oder um aus einer Endlosschleife herauszukommen.

Labels

Schleifensteuerbefehle ohne Labels beziehen sich auf die innerste Schleife, das heißt, sie unterbrechen die sie unmittelbar umschließende Schleife. Manchmal haben Sie aber mehrere ineinandergeschachtelte Schleifen und möchten unter einer bestimmten Bedingung aus mehreren Schleifen aussteigen. Aus diesem Grund können Sie Schleifen mit Labels kennzeichnen und dann mit last, next und redo zu diesen Schleifen springen.

Labels stehen am Anfang der Schleife und werden vereinbarungsgemäß großgeschrieben, damit man sie nicht mit Perl-Schlüsselwörtern durcheinanderbringt. Verwenden Sie einen Doppelpunkt, um das Label von der Schleife zu trennen:

LABEL: while (Bedingungsausdruck) {
#...
}

Innerhalb der Schleife verwenden Sie dann last, next oder redo mit dem Label:

LABEL: while (Bedingung_1) {
#...
while (Bedingung_2) {
# ...
if (noch_eine_Bedingung) {
last LABEL;
}
}
}

Ihre Labels können Sie nennen, wie Sie wollen, mit zwei Ausnahmen: BEGIN und END sind für die Paketkonstruktion und -dekonstruktion reserviert. Mit Paketen (englisch Packages) werden wir uns in diesem Buch nicht befassen; lediglich am Tag 13 werde ich Ihnen erklären, was ein Paket überhaupt ist. Wenn Sie bereit sind, in fortgeschrittene Perl-Konzepte einzusteigen, schauen Sie in der perlmod-Manpage nach weitergehenden Informationen über Pakete und Module.

Hier ein einfaches Beispiel ohne Labels. Die äußere while-Schleife überprüft, ob die Variable $exit ungleich dem String 'n' ist. Die innere while-Schleife ist eine Endlosschleife.

while ($exit ne 'n') {
while () {
print 'Geben Sie eine Zahl ein: ';
chomp($zahl = <STDIN>);
unless ($zahl eq '0' ) { # die Zahl 0 ist erlaubt
if ($zahl == 0 ) { # wenn Eingabe ein String
print "Keine Strings. Zahlen von 0 bis 9, bitte.\n";
next;
}
}
# andere Anweisungen
last;
}
print 'Eine weitere Zahl ausprobieren (j/n)?: ';
chomp ($exit = <STDIN>);
}

In diesem Beispiel verlassen next und last die sie unmittelbar einschließende Schleife - also die innere, endlose Schleife, in der Sie die Zahl eingeben. Die äußere Schleife wird nur in einem einzigen Fall verlassen: wenn Sie $exit am Ende der äußeren Schleife auf 'n' setzen.

Sagen wir, Sie wollten diesem Skript noch die Möglichkeit hinzufügen, dass man mit der Eingabe von exit alle Schleifen und das Skript beenden kann. Dazu würden Sie vor die äußere Schleife ein Label setzen:

AUSSEN: while ($exit ne 'n') {
# etc.
}

In der inneren Schleife würden Sie dann last mit dem Label verwenden:

AUSSEN: while ($exit ne 'n') {
while () {
print 'Geben Sie eine Zahl ein (Beenden mit exit): ';
chomp($zahl = <STDIN>);
if ($zahl eq "exit" ) { # schluss, aus, vorbei!
last AUSSEN;
}
# etc.
}
# mehr...
}

Wenn der Benutzer hier am 'Geben Sie eine Zahl ein'- Prompt »exit« eintippt, bezieht sich der last-Befehl auf die Schleife mit dem Label AUSSEN - beendet also in diesem Fall die äußere Schleife.

Beachten Sie, dass Schleifensteuerbefehle sich auf Schleifen beziehen und nicht auf eine bestimmte Position im Skript (wie etwa goto, falls Ihnen das etwas sagt). Am besten stellt man sich die Labels weniger als Sprungmarken, als vielmehr als Bezeichner für Schleifen, die man anspringen kann, vor. Wenn Sie mit einem Schleifensteuerbefehl aus einer Schleife herausspringen, springen Sie in Wirklichkeit nicht zu dem Label, sondern zur Anweisung direkt hinter der Schleife mit dem Label.

Die Variable $_

Glückwunsch! Sie wissen jetzt so gut wie alles, was Sie über Schleifen in Perl wissen müssen (das übrige erfahren Sie im Abschnitt »Vertiefung«). Weil wir in dieser Lektion noch etwas Platz haben, beende ich sie heute mit zwei allgemeinen Themen: der speziellen Variablen $_ und einer einfachen Methode, Dateien zu lesen. Beides werden wir in den weiteren Kapiteln dieses Buches häufig gebrauchen.

Fangen wir mit der $_-Variablen an. Diese spezielle Variable können Sie sich als einen Standardplatzhalter für skalare Werte vorstellen. Viele Perl-Konstrukte verwenden $_, wenn Sie keine eigene Skalarvariable angeben. Sie können das ausnutzen und Ihre Skripts kürzer und effizienter machen - manchmal allerdings auf Kosten der Lesbarkeit.

Betrachten wir ein Beispiel: foreach. Sie erinnern sich, dass die foreach-Schleife mit einer temporären Variablen arbeitet, in der die Listenelemente nacheinander zwischengespeichert werden. Wenn Sie keine temporäre Variable angeben, speichert Perl die Werte in $_ . Innerhalb der Schleife können Sie sich dann auf $_ beziehen, um an diesen Wert heranzukommen. Statt

foreach $key (sort keys %hash) {
print "Schlüssel: $key Wert: $hash{$key}\n";
}

können Sie genausogut schreiben:

foreach (sort keys %hash) {
print "Schlüssel: $_ Wert: $hash{$_}\n";
}

Auch viele Funktionen arbeiten mit dem Wert von $_ , wenn Sie ihnen keine Argumente übergeben - print und chomp zum Beispiel. Wundern Sie sich also nicht über Anweisungen wie:

print;

Fügen Sie einfach in Gedanken das $_ hinzu:

print $_;

Wir werden uns die $_-Variable noch genauer ansehen - im nächsten Abschnitt und am Tag 9.

Mit <> und while-Schleifen aus Dateien lesen

In den Beispielen der letzten Tage haben wir Eingabedaten mit <STDIN> von der Tastatur gelesen. Wir haben den Datei-Handle für die Standardeingabe sowohl in skalarem als auch in Listenkontext verwendet, und der Unterschied zwischen <STDIN> in skalarem Kontext (Zeile für Zeile) und Listenkontext (bis zum Dateiendezeichen) sollte Ihnen halbwegs klar sein.

Bestehen Ihre Eingabedaten allerdings aus mehr als nur ein paar Zeilen, ist es äußerst mühsam, sie alle über die Tastatur einzutippen. Das Statistikskript von gestern ist ein gutes Beispiel dafür - es dauert eine ganze Weile, alle Zahlen einzugeben, und wollten wir auch nur einen einzigen Wert hinzufügen, müßten wir komplett von vorne anfangen.

Viel praktischer wäre es, die Daten in eine eigene Datei zu speichern und von dort zu lesen, wenn das Skript ausgeführt wird. Dafür gibt es in Perl zwei Möglichkeiten: Zum einen können Sie eine bestimmte Datei innerhalb Ihres Skripts öffnen und lesen. Diesem Thema habe ich eine ganze Lektion gewidmet, nämlich Tag 15. Es gibt aber noch einen schnelleren Weg, Daten aus beliebigen Daten in ein Perl-Skript einzulesen. Diese Technik, die auf dem Perl-Befehl line beruht, bringe ich Ihnen heute bei.

Wir haben den Zeileneingabeoperator <> bis jetzt immer in Zusammenhang mit dem Datei-Handle <STDIN> verwendet. Wenn Sie <> aber ohne ein Datei-Handle einsetzen, holt Perl sich den Input aus der Datei, die Sie in der Kommandozeile angeben. Ein <> ohne Datei-Handle bedeutet im Grunde nichts anderes als: »Nimm alle in der Kommandozeile genannten Dateien, öffne sie, verkette sie miteinander und lies sie, als wären sie eine einzige Datei.«

Genaugenommen entnimmt Perl die Namen der zu öffnenden und zu lesenden Dateien von einer speziellen Variablen namens @ARGV, einem Array mit Dateinamen oder anderen in der Kommandozeile angegebenen Werten, und Sie könnten @ARGV im Skript noch verändern. Aber fürs erste nehmen wir einfach an, dass @ARGV die in der Kommandozeile angegebenen Dateinamen enthält und <> mit diesen arbeitet. Mehr über @ARGV erfahren Sie an Tag 15.

Hier ein Beispiel, das die in der Kommandozeile angegebenen Dateien zeilenweise einliest und jede Zeile nacheinander ausgibt:

while (defined($input = <>)) {
print "$input";
}

Wenn Sie mit MacPerl arbeiten, haben Sie vielleicht gar keine Kommandozeile und wissen nicht recht, was Sie jetzt machen sollen. Aber keine Angst, es geht auch in MacPerl: Speichern Sie Ihr Skript als Droplet (diese Option finden Sie im Save-Dialog, Menü Type). Ist Ihr Skript als Droplet gespeichert, können Sie die zu lesenden Dateien per Drag & Drop auf das Skript-Icon ziehen - MacPerl wird starten und diese Dateien in das Skript lesen.

Sagen wir, Sie hätten dieses Beispiel als echodatei.pl gespeichert und möchten die Datei eine_Datei.txt ausgeben. Von der Kommandozeile rufen Sie das Skript folgendermaßen auf:

% echodatei.pl eine_Datei.txt

Wenn Sie mehrere Dateien ausgeben wollen, hängen Sie einfach alle anderen Dateinamen hinten an die Kommandozeile an:

% echodatei.pl eine_Datei.txt noch_eine_Datei.txt Datei_3.txt

Perl wird all diese Dateien öffnen und eine nach der anderen ausgeben.

Gehen wir diese while-Schleifenbedingung noch einmal von innen nach außen durch, damit Sie verstehen, was hier vor sich geht. Das $input = <> wird Ihnen vertraut vorkommen; es ist ähnlich wie beim Lesen einer Zeile mit <STDIN> in skalarem Kontext. Vielleicht erinnern Sie sich noch, dass die defined-Funktion wahr oder falsch zurückgibt, je nachdem, ob ein Argument definiert ist oder nicht (das heißt, nicht den undefinierten Wert enthält). Hier verwenden wir defined, um die Schleife beim Dateiende abzubrechen - für jede Zeile der Datei bekommen wir einen gültigen Wert, doch am Ende der Datei liefert <> undefiniert zurück - $input wird ebenfalls undefiniert, die defined-Funktion liefert falsch, und die while-Schleife wird gestoppt.

Rein technisch gesehen brauchen Sie den defined-Teil gar nicht. Perl versteht auch so, wo das Dateiende ist, und hört automatisch auf zu lesen. Wenn Sie aber die Warnungen eingeschaltet haben, beschwert Perl sich, dass Sie nicht ausdrücklich auf das Dateiende geprüft haben. Sie können beides umgehen, die Warnung und den Aufruf von defined, wenn Sie $_ als Eingabevariable nehmen (siehe unten).

Wie bei <STDIN> können die leeren spitzen Klammern sowohl in skalarem als auch im Listenkontext verwendet werden. In skalarem Kontext lesen Sie die Eingabedateien zeilenweise ein (wobei das Zeilenende ein Zeilenvorschub oder - auf dem Mac - ein Carriage Return ist). Im Listenkontext wird jede Zeile der Datei (bzw. der Dateien) als ein Element der Liste gespeichert.

Eine noch kürzere Version des echodatei-Skripts macht von der $_-Variablen Gebrauch. Sie können die Variable $input durch $_ ersetzen und auf die temporäre Variable und die defined-Funktion komplett verzichten:

while (<>) {
print;
}

Wenn die Bedingung einer while-Schleife nichts als einen Zeileneingabeoperator enthält, liest diese while-Schleife die Eingabedateien Zeile für Zeile ein, weist jede Zeile nacheinander der Variablen $_ zu und bricht ab, wenn <> undefiniert ist, ohne dass Sie darauf ausdrücklich überprüfen müßten - Sie erhalten deswegen keine Fehlermeldung. Diesen Mechanismus können Sie eigentlich mit jeder Eingabequelle verwenden - es funktioniert auch mit <STDIN> oder einem anderen Datei-Handle.

Beachten Sie allerdings, dass es die while-Schleife ist - nicht der <>-Operator -, die die Zeilen nacheinander $_ zuweist und auf das Dateiende überprüft. Sie können zum Beispiel nicht do chomp(<>) schreiben; dieser Funktionsaufruf würde die aktuelle Zeile nicht in $_ speichern. Dieses besondere Feature haben nur while- und for-Schleifen (for-Schleifen werden als »verkleidete« while-Schleifen betrachtet).

Dieser Mechanismus ist ein sehr gebräuchlicher Weg, Daten in ein Perl-Skript einzulesen. In vielen Perl-Skripten werden Ihnen gleich am Skript-Anfang solche Schleifen begegnen, die Daten aus Dateien in ein Array oder einen Hash speichern.

Betrachten wir ein weiteres Beispiel für den Einsatz von <> und $_ zum Einlesen von Daten. Gestern hatten wir ein Skript, das den Benutzer um die Eingabe von ein paar Namen bittet und diese dann in ein Array speichert. Die Eingabeschleife dieses Skripts sah wie folgt aus:

while () {
print 'Geben Sie einen Namen ein (Vor- und Nachname): ';
chomp($in = <STDIN>);
if ($in ne '') {
($fn, $ln) = split(' ', $in);
$namen{$ln} = $fn;
}
else { last; }
}

Jetzt möchten wir diese Namen aus einer Datei einlesen. Zu diesem Zweck streichen wir die Eingabeaufforderung, den Aufruf von <STDIN> und die Überprüfung auf eine leere Eingabe:

while (defined($in = <>)) {
chomp($in);
($fn, $ln) = split(" ", $in);
$namen{$ln} = $fn;
}

Mit Hilfe der $_-Variablen können wir das Skript noch weiter kürzen:

while (<>) {
chomp;
($fn, $ln) = split(' ');
$namen{$ln} = $fn;
}

Obwohl sie kein einziges Mal explizit auftaucht, wird die $_-Variable hier sehr viel verwendet: Die while-Schleife weist ihr eine Zeile aus der Input-Datei zu und überprüft, ob dieser Wert definiert ist. Dann greift chomp auf diesen Wert zu und schneidet den Zeilenvorschub ab. Und split sieht in $_ nach, was es denn splitten soll. Derartige Abkürzungen sind in Perl-Skripten sehr häufig.

Wir könnten sogar die split-Funktion noch weiter abkürzen und das leere Argument weglassen. Ohne Argumente trennt split den String in $_ an den Leerstellen:

($fn, $ln) = split;

Vertiefung

Wie in den bisherigen Lektionen habe ich Ihnen auch zu Bedingungen und Schleifen noch nicht alles gesagt. Zum Teil möchte ich dies jetzt nachholen und Ihnen einige der bisher ausgelassenen Konzepte vorstellen. Ansonsten steht es Ihnen natürlich frei, auf eigene Faust weiterzuforschen.

Modifikatoren für Bedingungen und Schleifen

Alle Bedingungen und Schleifen, die Sie heute kennengelernt haben, sind (mit Ausnahme von do) komplexe Anweisungen - sie operieren auf Blöcken aus anderen Anweisungen und benötigen kein Semikolon am Zeilenende. Daneben besitzt Perl auch einen Satz sogenannter Modifikatoren (Modifier) für einfache Anweisungen, mit denen Sie bedingungs- und schleifenähnliche Anweisungen erstellen können. Manchmal helfen Modifikatoren, einfache Bedingungen und Schleifen noch kürzer auszudrücken oder die Logik einer Anweisung besser darzustellen.

Es gibt vier mögliche Modifikatoren: if, unless, while und until. Jeden dieser Modifier können Sie an eine einfache Anweisung anhängen, direkt vor dem Semikolon. Hier ein paar Beispiele:

print "$wert" if ($wert < 10);
$z = $x / $y if ($y > 0);
$i++ while ($i < 10);
print $wert until ($wert++ > $maxwert)

Mit einem Bedingungsmodifikator (if oder unless) wird der vordere Teil der Anweisung nur ausgeführt, wenn der Bedingungsausdruck wahr (oder bei unless falsch) ist. Mit einem Schleifenmodifikator (while oder until) wird die Anweisung so lange ausgeführt, wie der Ausdruck wahr (bzw. bei until falsch) ist.

Beachten Sie, dass Anweisungen mit while- oder until-Modifikatoren nicht dasselbe sind wie normale while- oder until-Schleifen. Sie können sie weder mit Schleifensteuerbefehlen wie next oder last kontrollieren noch ihnen Schleifenlabels zuweisen.

Die do-Schleifen, die Sie vorhin in diesem Abschnitt kennengelernt haben, sind genau genommen Anweisungen mit Schleifenmodifikatoren. Do ist eine Funktion, die einen Anweisungsblock ausführt. Mit den Modifikatoren while und until bestimmen Sie, wie oft diese Ausführung wiederholt wird. Deshalb können Sie in do-Schleifen keine Schleifensteuerbefehle verwenden.

continue-Blöcke

Ein continue-Block ist ein optionaler Anweisungsblock nach einer Schleife, der ausgeführt wird, wenn der Schleifenblock zu Ende ausgeführt oder die Schleife mit next unterbrochen wurde. Der continue-Block wird nicht ausgeführt, wenn Sie die Schleife mit last oder redo abbrechen. Nach der Ausführung des continue-Blocks macht Perl mit dem nächsten Schleifendurchlauf (also der Auswertung der Schleifenbedingung) weiter.

Sie könnten einen continue-Block zum Beispiel verwenden, um einen Fehler zu beheben, der die Schleife unterbrochen hat. Danach läuft die Schleife normal weiter. Das ganze sieht folgendermaßen aus (wobei die while-Schleife hier auch eine for- oder foreach-Schleife sein könnte):

while ( Bedingung ) {
# Anweisungen
if (noch eine Bedingung) {
# Fehler!
next; # (springt zum continue-Block)
}
# weitere Anweisungen
} continue {
# behebe Fehler
# (danach geht's am Schleifenanfang weiter)
}

Beachten Sie, dass continue in Perl etwas anderes ist als das continue in C - dessen Perl-Pendant ist next.

Konstruktion von switch- oder case-Anweisungen

Bemerkenswerterweise hat Perl keine explizite switch- (oder, je nach Ihrer Lieblingssprache, auch case-) Anweisung. Eine switch-Anweisung (Schalter, Weiche) ist eine Fallunterscheidung, mit der Sie einen Wert überprüfen, und viel kompakter, übersichtlicher und oft auch effizienter als eine Konstruktion aus zahllosen if und elsif, um festzulegen, was bei welchem Ergebnis geschehen soll.

Ein solches switch-Konstrukt können Sie in Perl jedoch »nachahmen«. Hier erweisen sich freistehende Blöcke (bare blocks) als äußerst nützlich. Für freistehende Blöcke gelten die gleichen Regeln wie für Schleifenblöcke; deswegen können Sie sie mit Labels versehen, mit redo erneut ausführen oder mit last und next verlassen (next führt einen eventuell vorhandenen continue-Block aus, last nicht). Das nutzen wir aus und schreiben unser »Perl-Switch« zum Beispiel so:

SWITCH: {
$a eq "eins" && do {
$a = 1;
last SWITCH;
};
$a eq "zwei" && do {
$a = 2;
last SWITCH;
};
$a eq "drei" && do {
$a = 3;
last SWITCH;
};
# und so weiter
}

Mit Pattern Matching (Mustervergleichen) geht es noch kürzer. Ich komme darauf zurück, wenn wir uns an Tag 9 mit Pattern Matching und regulären Ausdrücken befassen.

goto

Ja, Perl unterstützt das verschriene goto (englisch »gehe zu«). Es ist in keinem Fall empfehlenswert, doch wenn Sie denn unbedingt wollen, können Sie goto auf drei Arten einsetzen:

Falls Sie sich für weitere goto-Details interessieren, schauen Sie in die perlsyn- Manpage.

Zusammenfassung

Mit Bedingungen und Schleifen können Sie je nach Situation entscheiden, wie sich Ihr Skript verhalten soll. Diese Kontrollstrukturen sind so wichtig, dass wir schon seit dem zweiten Tag - lange bevor wir zu dieser Lektion gekommen sind - in den Beispielen mit ihnen gearbeitet haben.

Bedingungsanweisungen zweigen zu unterschiedlichen Anweisungsblöcken ab, abhängig davon, ob eine Bedingung erfüllt ist oder nicht. Sie haben die Konstrukte if, if...else und if...elsif kennengelernt, den Bedingungsoperator (?...:), der in andere Ausdrücke eingebaut werden kann, und Sie haben gesehen, wie Sie logische Operatoren (&&, ||, and und or) zur Steuerung des Programmflusses einsetzen können.

Als nächstes haben wir Schleifen behandelt: insbesondere die while-Schleifen, die einen Anweisungsblock so lange ausführen, wie die Schleifenbedingung wahr ist. Sie haben das Gegenstück, die until-Schleife, kennengelernt, die abbricht, wenn die Bedingung wahr ist. In diesem Zusammenhang haben wir auch die do-Schleifen (do...while und do...until) und ihre Besonderheiten besprochen - beispielsweise dass sie gar keine echten Schleifen sind.

Die zweite Art von Schleife ist die for-Schleife, die ebenfalls Anweisungsblöcke wiederholt, aber geeigneter ist, wenn die Anzahl der Durchläufe von vornherein feststeht. Sie haben die for-Schleife mit ihrer C-ähnlichen Zählersyntax und die foreach-Schleife zum Durchlaufen aller Elemente in einer Liste kennengelernt.

Dann habe ich erklärt, wie Sie mit den Schleifensteuerbefehlen next, last und redo die Ausführung eines Blocks abbrechen und Teile der Schleife überspringen sowie in verschachtelten Schleifen eine bestimmte, mit einem Label versehene Schleife ansteuern können.

Schließlich haben wir die Lektion wie die beiden letzten Lektionen mit weiteren Anmerkungen zum Thema Eingabe beendet. Diesmal haben Sie gelernt, wie Sie mit der <>-Syntax aus Dateien lesen und mit der Variablen $_ viele Operationen abkürzen können.

Mit der heutigen Lektion haben Sie sich die Grundlagen zu Perl vollständig angeeignet. Morgen werden wir die Woche mit einigen längeren Beispielen abschließen. Nächste Woche geht es nicht bloß weiter, nächste Woche legen wir richtig los und widmen uns einigen der mächtigsten und aufregendsten Perl-Features, darunter Pattern Matching (Mustervergleiche) und allerlei Listenakrobatik.

Sie haben heute folgende Perl-Funktionen kennengelernt:

Mehr Details finden Sie - wie bereits erwähnt - in der perlfunc-Manpage.

Fragen und Antworten

Frage:
Ich habe den Eindruck, dass for-Schleifen auch als while-Schleifen geschrieben werden könnten und umgekehrt.

Antwort:
Ja, das könnten sie mit Sicherheit. Aber es geht darum, was Sie sich vorstellen, eine »wiederhole x-mal«- oder eine »wiederhole so lange, bis«- Schleife. Sie sollen dann nicht noch darüber nachdenken müssen, wie Sie die eine in die andere umformulieren. Es ist eine der angenehmsten Eigenschaften von Perl, dass es in der Lage ist, sich an Ihre Denkweise anzupassen. Übrigens kennen sogar die weit weniger »entgegenkommenden« Sprachen C und Java while- und for-Schleifen.

Frage:
Ich habe versucht, mit continue eine Schleife abzubrechen, und Perl hat mit Fehlermeldungen um sich geworfen. Was habe ich falsch gemacht?

Antwort:
Sie haben vergessen, dass continue in Perl nicht zum Abbrechen von Schleifen verwendet wird (oder Sie haben diesen Abschnitt übersprungen). Die Entsprechung zum continue von C ist in Perl next. Verwenden Sie continue nur als optionalen Anweisungsblock, der am Ende eines Blocks ausgeführt werden soll (siehe Vertiefungsabschnitt).

Frage:
Ich habe mir Ihre beiden Beispiele für das Lesen aus Dateien angesehen. Eines der Beispiele verwendet $_, das andere nicht. Das erste ist zwar kürzer, aber wenn man nicht weiß, welche Operationen mit $_ arbeiten, ist es kaum zu verstehen. Ich finde, dass die Lesbarkeit eines Skripts ein paar mehr Buchstaben wert ist.

Antwort:
Das ist definitiv ein guter Grundsatz, dem auch viele Perl-Programmierer folgen. »Schleichwege« über die $_-Variable machen das Skript zwar kürzer, aber bestimmt auch weniger verständlich. In manchen Fällen - wie zum Beispiel für Eingaben mit <> - ist der Einsatz von $_ jedoch eine Art weitverbreitete »Redensart«, die, sobald Sie sich daran gewöhnt haben, auch vernünftig und verständlich scheint. Wenn Sie Skripts von anderen lesen oder verändern müssen, werden Sie wahrscheinlich recht bald auf mysteriöses $_- Verhalten stoßen. Achten Sie also auf $_, setzen Sie es ein, wo es wirklich angemessen ist, oder vermeiden Sie es, wenn es Ihrer Meinung nach die Lesbarkeit zu sehr einschränkt. Die Entscheidung liegt ganz bei Ihnen.

Workshop

Der Workshop enthält Quizfragen, die Ihnen helfen sollen, Ihr Wissen zu festigen, und Übungen, die Sie anregen sollen, das eben Gelernte umzusetzen und eigene Erfahrungen zu sammeln. Versuchen Sie, das Quiz und die Übungen zu beantworten und zu verstehen, bevor Sie zur Lektion des nächsten Tages übergehen.

Quiz

  1. Was ist ein Block? Was kann ein Block enthalten?
  2. Was ist der Unterschied zwischen if und unless?
  3. Wodurch wird eine while-Schleife abgebrochen?
  4. Warum sind do-Schleifen anders als while- oder until-Schleifen?
  5. Welche drei Ausdrücke enthält die Klammer der for-Schleife? Was machen sie?
  6. Erklären Sie den Unterschied zwischen next, last und redo.
  7. Nennen Sie drei Situationen, in denen die Variable $_ verwendet werden kann.
  8. Worin unterscheidet sich <> von <STDIN>?

Übungen

  1. Schreiben Sie ein Divisions-Skript, in dem Sie um die Eingabe von zwei Zahlen bitten und prüfen, dass keine der eingegebenen Zahlen negativ und die zweite Zahl nicht Null ist. Wenn beide Zahlen diesen Anforderungen entsprechen, teilen Sie die erste durch die zweite Zahl, und geben Sie das Ergebnis aus.
  2. FEHLERSUCHE: Was stimmt nicht an diesem Skript? (Tipp: Es könnten auch mehrere Fehler sein)
        if ($wert == 4) then { print $wert; }
    elseif ($wert > 4) { print "mehr als 4"; }
  3. FEHLERSUCHE: Und was ist falsch an diesem hier (es könnten auch hier mehrere Fehler sein)?
        for ($i = 0, $i < $max, $i++) {
    $werte[$i] = "";
    }
  4. FEHLERSUCHE: Und was ist mit diesem hier?
        while ($i < $max) {
    $werte[$i] = 0;
    }
  5. Schreiben Sie das Skript namen.pl so um, dass es die Namen aus einer Datei liest (wenn Sie gestern die Übungsaufgabe 3 gelöst haben, also auch mit Zweitnamen umgehen können, schreiben Sie diese Version entsprechend neu).

Antworten

Hier die Antworten auf die Workshop-Fragen aus dem vorigen Abschnitt.

Antworten zum Quiz

  1. Ein Block ist eine von geschweiften Klammern ({}) umgebene Gruppe von Perl- Anweisungen (die andere Blöcke enthalten kann). Blöcke werden meistens im Zusammenhang mit Bedingungen und Schleifen gebraucht, können aber auch allein, als freistehende Blöcke, verwendet werden (bare blocks).
  2. Eine if-Anweisung führt ihren Block aus, wenn der Bedingungsausdruck zu wahr ausgewertet wird, unless führt ihn aus, wenn die Bedingung falsch ergibt.
  3. Die while-Schleife bricht ab, wenn ihr Bedingungsausdruck falsch zurückgibt.
  4. do-Schleifen unterscheiden sich in zweierlei Hinsicht von while- oder until- Schleifen. Zum einen werden ihre Blöcke vor der ersten Überprüfung, also mindestens einmal, ausgeführt. Zum anderen können Sie keine Schleifensteuerbefehle wie last oder next in einer do-Schleife verwenden, weil do gar keine echte Schleife, sondern eigentlich eine Funktion mit einem Modifikator ist.
  5. Die drei Teile in den Klammern der for-Schleife sind:
  6. a) Ein Ausdruck zum Initialisieren der Laufvariablen/des Schleifenzählers, wie $i = 0.
  7. b) Ein Bedingungsausdruck, der die Anzahl der Durchläufe bestimmt, wie zum Beispiel $i < $max.
  8. c) Ein Inkrementierungsausdruck, der den Schleifenzähler verändert (damit die Schleifenbedingung irgendwann nicht mehr erfüllt ist), zum Beispiel $i++.
  9. next, last und redo sind Schleifensteuerbefehele. next bricht den aktuellen Durchlauf ab und startet am Schleifenanfang den nächsten, inklusive der Überprüfung der Bedingung in einer for- oder while-Schleife. Auch redo bricht den aktuellen Durchlauf ab, startet aber am Anfang des Blocks, ohne die Schleifenbedingung zu überprüfen. last beendet die Schleife ebenso komplett wie abrupt - es prüft nichts, startet nichts, es steigt einfach aus.
  10. Hier ein paar Situationen, in denen Perl $_ verwendet, wenn Sie keine Variable angegeben:
  11. while (<>) weist $_ jede Zeile einzeln zu.
  12. chomp entfernt den Zeilenvorschub vom String in $_.
  13. foreach verwendet $_ als temporäre Schleifenvariable.
  14. <> wird verwendet, um die Inhalte von Dateien einzulesen, die über die Kommandozeile angegeben (und in @ARGV gespeichert) werden. Mit <STDIN> liest man Daten von der Standardeingabe (normalerweise der Tastatur) ein.

Lösungen zu den Übungen

  1. Hier eine mögliche Lösung:
        #!/usr/bin/perl -w

    $zahl1 = 0;
    $zahl2 = 0;

    while () {
    print 'Geben Sie die erste Zahl ein: ';
    chomp($zahl1 = <STDIN>);
    print 'Geben Sie die zweite Zahl ein: ';
    chomp($zahl2 = <STDIN>);

    if ($zahl1 < 0 || $zahl2 < 0) {
    print "Keine negativen Zahlen!\n";
    next;
    } elsif ( $zahl2 == 0) {
    print "Die zweite Zahl darf nicht 0 sein!\n";
    next;
    } else { last; }
    }

    print "$zahl1 geteilt durch $zahl2 ist ";
    printf("%.2f\n", $zahl1 / $zahl2 );
  2. Es sind zwei Fehler: elseif ist kein gültiges Perl-Schlüsselwort, nehmen Sie statt dessen elsif. then ist ebenfalls kein gültiges Perl-Schlüsselwort.
  3. Die Ausdrücke innerhalb des for-Bedingungsausdrucks müssen mit Semikola, nicht mit Kommata getrennt werden.
  4. Syntaktisch ist diese Schleife korrekt, aber sie wird nie enden - $i wird innerhalb der Schleife nicht inkrementiert.
  5. Hier eine Antwort:
        #!/usr/bin/perl -w

    %names = (); # Hash: Namen
    @raw = (); # temp: rohe Woerter
    $fn = ''; # Vorname

    while (<>) {
    chomp;
    @raw = split(" ", $_);
    if ($#raw == 1) { # Normalfall: zwei Woerter
    $names{$raw[1]} = $raw[0];
    } else { # den Vornamen zusammensetzen
    $fn = '';
    for ($i = 0; $i < $#raw; $i++) {
    $fn .= $raw[$i] . " ";
    }
    $names{$raw[$#raw]} = $fn;
    }
    }

    foreach $lastname (sort keys %names) {
    print "$lastname, $names{$lastname}\n";
    }


vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


© Markt&Technik Verlag, ein Imprint der Pearson Education Deutschland GmbH